#ifndef __tenacitas_log__logger__h__
#define __tenacitas_log__logger__h__


// =============================================================================
/** @file */

// =============================================================================
/** todo
    @todo parameterize log maintenance algorithm
    @todo allow definition of level from which messages should always be logged
    @todo class @p logger_binary and its consequences
*/


// =============================================================================
//                                 C++ Headers
#include <string>
#include <cstdint>
#include <mutex>
#include <map>
#include <utility>
#include <sstream>
#include <memory>
#include <functional>

// =============================================================================
//                                 3rds Headers
#include <boost/thread/thread.hpp>

// =============================================================================
//                                 Our Headers
#include "level.h"

// =============================================================================
//                                 Namespaces
using namespace std;


// =============================================================================
//                                 Macro Contants
/** */


// =============================================================================
//                                 Macro Commands
/** */
#define log_trace( logger ) logger.new_line( tenacitas::log::level::trace, __FILE__, __func__, __LINE__ )

#define log_debug( logger ) logger.new_line( tenacitas::log::level::debug, __FILE__, __func__, __LINE__ )

#define log_info( logger ) logger.new_line( tenacitas::log::level::info, __FILE__, __func__, __LINE__ )

#define log_warn( logger ) logger.new_line( tenacitas::log::level::warn, __FILE__, __func__, __LINE__ )

#define log_error( logger ) logger.new_line( tenacitas::log::level::error, __FILE__, __func__, __LINE__ )

#define log_critical( logger ) logger.new_line( tenacitas::log::level::critical, __FILE__, __func__, __LINE__ )


/** namespace of the organization */
namespace tenacitas {
    namespace log {

        // =========================== Typedefs ===========================

        // ============================ Pre-Declarations ==================
        template <typename t_printer_factory>
        class logger_factory_t;

        // ============================ Attributes ========================
        /** */

        // ============================ Functions =========================
        /** */

        // ============================ Classes ===========================
        /** 

            @nosubgrouping 
        */
        template <typename t_printer_factory>
        class logger_t {

            //    logger - Friends

            /** */
            friend class logger_factory_t<t_printer_factory>;

        public:

            //    logger - Public Internal Types 

            /** */
            typedef t_printer_factory printer_factory;

            /** */
            typedef typename printer_factory::ptr printer_factory_ptr;

            /** */
            typedef typename printer_factory::printer printer;

            /** */
            typedef typename printer_factory::printer_ptr printer_ptr;
	  
            //    logger - Public Constructors

            /** */
            ~logger_t( ) { }

            /** */
            logger_t ( const logger_t & p_logger ) = delete;

            /** */
            logger_t ( logger_t && p_logger ) = delete;
            

            //    logger - Public Accessors

            /** */
            void set_level( const level & p_level ) {
                m_level = p_level;
            }

            /** */
            const level & get_level( ) const {
                return m_level;
            }

            /** */
            bool can_log( const level & p_level ) const {
                return ( p_level >= m_level );
            }
	    
            //    logger - Public Processors

            /** */
            logger_t & new_line( level p_level, const char * p_file,
                                 const char * p_function, int64_t p_line ) {
                if ( can_log( p_level ) ) {
                    m_printer->new_line( p_level, p_file, p_function, p_line );
                }
                return *this;
            }

            //    logger - Public Operators

            /** */
            logger_t & operator = ( const logger_t & p_logger ) = delete;

            /** */
            logger_t & operator = ( logger_t && p_logger ) = delete;

            /** */
            bool operator == ( const logger_t & p_logger ) const {
                return ( m_thread_id == p_logger.m_thread_id );
            }

            /** */
            bool operator != ( const logger_t & p_logger ) const {
                return ( m_thread_id != p_logger.m_thread_id );
            }

            /** */
            bool operator < ( const logger_t & p_logger ) const {
                return ( m_thread_id < p_logger.m_thread_id );
            }

            /** */
            bool operator > ( const logger_t & p_logger ) const {
                return ( m_thread_id > p_logger.m_thread_id );
            }

            /** */
            bool operator <= ( const logger_t & p_logger ) const {
                return ( m_thread_id <= p_logger.m_thread_id );
            }

            /** */
            bool operator >= ( const logger_t & p_logger ) const {
                return ( m_thread_id >= p_logger.m_thread_id );
            }

            /** */
            template <typename t_data>
            logger_t & operator << ( const t_data & p_data ) {
                m_printer->write( p_data );
                return *this;
            }

        private:

            //    logger - Private Constructors 

            /** */
            logger_t( boost::thread::id p_thread_id,
                      printer_ptr p_printer,
                      const level & p_level = level::info )
                : m_thread_id( p_thread_id ),
                  m_printer( p_printer ),
                  m_level( p_level ) { }

            //    logger - Private Attributes 

            /** */
            boost::thread::id m_thread_id;

            /** */
            printer_ptr m_printer;

            /** */
            level m_level;
        };

        /** 

            @nosubgrouping 
        */
        template <typename t_printer_factory>
        class logger_factory_t {

        public:

            //    logger_factory - Public Internal Types

            /** */
            typedef t_printer_factory printer_factory;

            /** */
            typedef typename printer_factory::ptr printer_factory_ptr;

            /** */
            typedef typename printer_factory::printer printer;

            /** */
            typedef typename printer_factory::printer_ptr printer_ptr;

            /** */
            typedef logger_t<printer_factory> logger;

            /** */
            typedef std::shared_ptr<logger_factory_t> ptr;

            //    logger_factory - Public Constructor

            /** */
            logger_factory_t( printer_factory && p_printer_factory,
                              const level & p_level = level::info )
                : m_printer_factory( std::move( p_printer_factory ) ),
                  m_threads_loggers( ),
                  m_mutex( ),
                  m_level( p_level ) {
            }

            /** not allowed */
            logger_factory_t( const logger_factory_t & ) = delete;

            /** Destructor */
            ~logger_factory_t( ) {}

            //    logger_factory - Public Operators 

            /** Sets log level for all the @p logger objects */
            void set_level( const level & p_level ) {
                std::lock_guard<std::mutex> lock(m_mutex);
                typename threads_loggers::iterator l_ite =
                    m_threads_loggers.begin( ); 
                
                typename threads_loggers::iterator l_end =
                    m_threads_loggers.end( );
                
                for ( ; l_ite != l_end; ++l_ite ) {
                    l_ite->second->set_level( p_level );
                }
            }


            //    logger_factory - Public Operators 

            /** not allowed */
            logger_factory_t & operator =( const logger_factory_t & ) = delete;

            //    logger_factory - Public Processors 

            /** */
            logger & create( ) {
                boost::thread::id l_th_id ( boost::this_thread::get_id( ) );

                // already created?
                typename threads_loggers::iterator l_ite =
                    m_threads_loggers.find( l_th_id ); 

                if ( l_ite != m_threads_loggers.end( ) ) {
                    // yes!
                    return ( *l_ite->second );
                }

                // inserting the logger into the collection of loggers,
                // associating to theirs threads

                printer_ptr l_printer = m_printer_factory.create( l_th_id );

                logger_ptr
                    l_logger( new logger ( l_th_id, l_printer, m_level ) ); 

                std::pair<boost::thread::id, logger_ptr> l_pair( l_th_id, l_logger ); 

                std::lock_guard<std::mutex> lock(m_mutex);

                // I can lock only here, because the thread l_th_id can not be
                // modified in another thread
                boost::this_thread::at_thread_exit( at_thread_end( this ) );

                // returning a reference to the just inserted logger
                return *( m_threads_loggers.insert( l_pair ).first->second );
            }

        private:

            //    logger_factory - Private Internal Types

            /** Function object to be called when a thread ends */
            struct at_thread_end {
                at_thread_end( logger_factory_t * p_logger_factory )
                : m_logger_factory( p_logger_factory ) {}

                at_thread_end( const at_thread_end & p_at_thread_end )
                : m_logger_factory( p_at_thread_end.m_logger_factory ) {}
                
                at_thread_end( at_thread_end && p_at_thread_end )
                : m_logger_factory( p_at_thread_end.m_logger_factory ) {}

                at_thread_end & operator=( at_thread_end && p_at_thread_end ) {
                    m_logger_factory = p_at_thread_end.m_logger_factory;
                    return *this;
                }

                at_thread_end & operator=( const at_thread_end & p_r ) {
                    m_logger_factory = p_r.m_logger_factory;
                    return *this;
                }

                void operator( )( ) {
                    m_logger_factory->delete_logger( boost::this_thread::get_id( ) );
                }

            private:
                logger_factory_t * m_logger_factory;
            };
            
            /** */
            typedef typename std::shared_ptr<logger> logger_ptr;

            /** */
            typedef std::map<boost::thread::id, logger_ptr> threads_loggers;

            /** */
            typedef typename threads_loggers::iterator loggers_iterator;
            

            //    logger_factory - Private Accessors

            /** */
            void delete_logger( boost::thread::id p_thread_id ) {

                loggers_iterator l_ite = m_threads_loggers.find( p_thread_id );

                if ( l_ite != m_threads_loggers.end( ) ) {

                    // I can lock only here, because exclusion of p_thread_id can
                    // not happen in another thread
                    std::lock_guard<std::mutex> lock(m_mutex);

                    m_threads_loggers.erase( l_ite );
                }
            }
                

            //    logger_factory - Private Attributes

            /** */
            printer_factory m_printer_factory;

            /** */
            threads_loggers m_threads_loggers;

            /** */
            std::mutex m_mutex;

            /** */
            level m_level;

        };
    }
}
#endif
